﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;

namespace Best.SmartCms.Utility
{
    public class MemoryCacheManager 
    {
        private static readonly MemoryCache _cache ;
        private static object _lockObj = new object();
        /// <summary>
        /// 
        /// </summary>
        private static CancellationTokenSource _cancellationTokenSource;

        /// <summary>
        /// 
        /// </summary>
        private static readonly ConcurrentDictionary<string, bool> _allKeys;

        static MemoryCacheManager()
        {
            _allKeys = new ConcurrentDictionary<string, bool>();
            if (_cache == null)
            {
                lock (_lockObj)
                {
                    if (null == _cache)
                    {
                        _cache = new MemoryCache(new MemoryCacheOptions());
                    }
                }
            }
            _cancellationTokenSource = new CancellationTokenSource();

        }

    
        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheTime"></param>
        /// <returns></returns>
        public static MemoryCacheEntryOptions GetMemoryCacheEntryOptions(int cacheTime)
        {
            var options = new MemoryCacheEntryOptions()
                .AddExpirationToken(new CancellationChangeToken(_cancellationTokenSource.Token))
                .RegisterPostEvictionCallback(PostEviction);

            options.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(cacheTime);

            return options;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cacheTime"></param>
        /// <returns></returns>
        public static MemoryCacheEntryOptions GetMemoryCacheEntryOptions(TimeSpan cacheTime)
        {
            var options = new MemoryCacheEntryOptions()
                .AddExpirationToken(new CancellationChangeToken(_cancellationTokenSource.Token))
                .RegisterPostEvictionCallback(PostEviction);

            options.AbsoluteExpirationRelativeToNow = cacheTime;

            return options;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string AddKey(string key)
        {
            _allKeys.TryAdd(key, true);
            return key;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string RemoveKey(string key)
        {
            TryRemoveKey(key);
            return key;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        public static void TryRemoveKey(string key)
        {
            if (!_allKeys.TryRemove(key, out bool _))
                _allKeys.TryUpdate(key, false, false);
        }

        public static void ClearKeys()
        {
            foreach (var key in _allKeys.Where(p => !p.Value).Select(p => p.Key).ToList())
            {
                RemoveKey(key);
            }
        }

        public static void PostEviction(object key, object value, EvictionReason reason, object state)
        {
            if (reason == EvictionReason.Replaced)
                return;

            ClearKeys();

            TryRemoveKey(key.ToString());
        }


        public static void RemoveByPattern(string pattern, IEnumerable<string> keys)
        {
            var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
            var matchesKeys = keys.Where(key => regex.IsMatch(key)).ToList();

            matchesKeys.ForEach(Remove);
        }

        #region Methods

        /// <inheritdoc />
        public static T Get<T>(string key)
        {
            return _cache.Get<T>(key);
        }

        /// <inheritdoc />
        public static void Set(string key, object data, int cacheTime)
        {
            if (data != null)
            {
                _cache.Set(AddKey(key), data, GetMemoryCacheEntryOptions(cacheTime));
            }
        }

        /// <inheritdoc />
        public bool IsSet(string key)
        {
            return _cache.TryGetValue(key, out object _);
        }

        /// <inheritdoc />
        public static void Remove(string key)
        {
            _cache.Remove(RemoveKey(key));
        }

        /// <inheritdoc />
        public static void RemoveByPattern(string pattern)
        {
            RemoveByPattern(pattern, _allKeys.Where(p => p.Value).Select(p => p.Key));
        }

        /// <inheritdoc />
        public static long IncrementValue(string key, int cacheTime)
        {
            lock (_lockObj)
            {
                if (_cache.TryGetValue(key, out object _))
                {
                    var value = Get<long>(key);
                    var res = value + 1;
                    Set(key, res, cacheTime);
                    return res;
                }

                Set(key, 1L, cacheTime);
                return 1;
            }
        }

        /// <inheritdoc />
        public static bool HashSet(string key, string hashId, object value, TimeSpan expireIn, CacheWhenEnum when)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public static T HashGet<T>(string key, string hashId)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public static bool HashExists(string key, string hashId)
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        public static bool Set(string key, object data, TimeSpan span, CacheWhenEnum when)
        {
            var res = true;
            if (when == CacheWhenEnum.Always)
            {
                _cache.Set(AddKey(key), data, GetMemoryCacheEntryOptions(span));
                return true;
            }
            var mid = _cache.TryGetValue(key, out object _);
            if (when == CacheWhenEnum.NotExists)
            {
                if (mid)
                {
                    res = false;
                }
            }
            else
            {
                res = mid;
            }
            if (res)
                _cache.Set(key, data, GetMemoryCacheEntryOptions(span));
            return res;
        }

        #endregion Methods

        public enum CacheWhenEnum
        {
            /// <summary>
            /// 
            /// </summary>
            Always = 0,
            /// <summary>
            /// 
            /// </summary>
            Exists = 1,
            /// <summary>
            /// 
            /// </summary>
            NotExists = 2
        }
    }
}
